fix(sdk): parent_trace_id contextvar ALWAYS overrides caller-set (0.13.8 hotfix #2)#66
Merged
Conversation
…3.8 hotfix #2) Hotfix #2 for the parent_trace_id wire-add end-to-end work from PR #64 / 0.13.7. PR #64 wired the field on the wire, but a diagnostic script (sdk_diag.py) running on the deployed 0.13.7 revealed cost_events on the backend were missing parent_trace_id: trace_id=cccccccc-... parent_trace_id=NULL model=gpt-4.1-mini tokens=10 The drift was in _enrich_event's parent_trace_id fallback (commit efff530): it used an "if not in enriched" guard, so when langgraph.py::on_llm_end's _active_runs[run_id] lookup missed (run_id drift between the auto-injected chat_model callback and an explicit user-supplied one, or no matching on_llm_start because the user wrapped the LLM call in a non-langgraph stack), the field was absent, the trace_id fallback at line 2422 overwrote the event with the chain contextvar, but parent_trace_id stayed NULL because the old condition was skipped. Override semantics: the chain contextvar is the single source of truth for "what chain does this event belong to". Both langgraph.py::on_llm_end's caller-set value and a non-langgraph caller's absence resolve to the same contextvar; preferring the contextvar when present is idempotent for the happy path AND closes the drift in the unhappy path. Also: - Bump version 0.13.7 -> 0.13.8. - Prepend v3.23 / 0.13.8 changelog entry. - Rewrite the existing test_enrich_event_preserves_caller_set_parent_trace_id to match the new override semantics. - Add 2 new regression tests in TestEnrichEventParentTraceOverride: test_enrich_event_sets_parent_trace_id_when_chain_contextvar_set (drift scenario) and test_enrich_event_parent_trace_id_matches_trace_id_in_chain_mode (SpanContext invariant). Regression coverage: 24 tests in test_drift_fixes_2026_07_04.py (22 prior + 2 new). Full critical suite 157/157 passed. ruff clean, mypy 34/34 source files clean. End-to-end after this hotfix: the next real LLM call inside a chain contextvar should arrive at the backend with both cost_events.trace_id and cost_events.parent_trace_id set to the chain contextvar value, and the unified SELECT third JOIN arm should populate the dashboard's Recent executions panel with Model / Tokens / Cost on the orchestration row.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hotfix #2 for PR #64. Closes the drift bug where cost_events.parent_trace_id stayed NULL on the backend even though cost_events.trace_id carried the chain contextvar value.
Root cause was an "if not in enriched" guard in _enrich_event that skipped the parent_trace_id fallback when langgraph.py::on_llm_end's _active_runs[run_id] lookup missed. Fix: always override from the chain contextvar when one is in scope. The chain contextvar is the single source of truth for "what chain does this event belong to" — both langgraph callback's caller-set value and a non-langgraph caller's absence resolve to the same contextvar, so preferring the contextvar when present is idempotent for the happy path AND closes the drift in the unhappy path.
Regression coverage: 2 new tests in TestEnrichEventParentTraceOverride (drift scenario + SpanContext invariant). 24/24 tests in test_drift_fixes_2026_07_04.py, 157/157 critical suite, ruff clean, mypy 34/34 source files clean.
End-to-end after merge: the next real LLM call inside a chain contextvar arrives at the backend with both cost_events.trace_id and cost_events.parent_trace_id set, and the unified SELECT third JOIN arm populates the dashboard's Recent executions panel with Model / Tokens / Cost on the orchestration row.